/**
 * GameManager.java
 *
 * Roslan's Remake of MOO in 3D using Ardor3D
 * (C) 2011 Roslan Amir. All rights reserved.
 */

package com.roslan.games.moo3d;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Iterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ardor3d.math.ColorRGBA;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Vector3;
import com.ardor3d.math.type.ReadOnlyColorRGBA;
import com.ardor3d.math.type.ReadOnlyVector3;
import com.ardor3d.renderer.state.MaterialState;
import com.ardor3d.scenegraph.Line;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.Node;
import com.roslan.games.moo3d.GameData.Actions;
import com.roslan.games.moo3d.GameData.GameStates;
import com.roslan.games.moo3d.data.Colony;
import com.roslan.games.moo3d.data.Race;
import com.roslan.games.moo3d.data.Star;
import com.roslan.games.moo3d.events.ColonizeEvent;
import com.roslan.games.moo3d.events.Event;
import com.roslan.games.moo3d.events.ExploreEvent;
import com.roslan.games.moo3d.spatial.FleetHighlight;
import com.roslan.games.moo3d.spatial.FleetNode;
import com.roslan.games.moo3d.spatial.FleetNode.FleetStatus;
import com.roslan.games.moo3d.spatial.FleetRoute;
import com.roslan.games.moo3d.spatial.StarHighlight;
import com.roslan.games.moo3d.spatial.StarNode;
import com.roslan.games.moo3d.ui.hud.HudManager;
import com.roslan.games.moo3d.ui.menu.MenuManager;
import com.roslan.games.moo3d.utils.HexGridUtils;

/**
 * Comments go here.
 * 
 * @author Roslan Amir
 * @version 1.0 - Jan 24, 2011
 */
public class GameManager {

	/**
     * 
     */
	private static final Logger logger = LoggerFactory.getLogger(Base.class);

	/**
	 * The singleton instance of this class.
	 */
	private static GameManager instance;

	/**
	 * Reference to the single instance of the GameData class.
	 */
	private final GameData gameData = GameData.getInstance();

	/**
	 * Reference to the single instance of the <code>MenuManager</code> class.
	 */
	private final MenuManager menuManager = MenuManager.getInstance();

	/**
	 * Reference to the single instance of the <code>HudManager</code> class.
	 */
	private final HudManager hudManager = HudManager.getInstance();

	/**
	 * Reference to the single instance of the <code>FleetManager</code> class.
	 */
	private final FleetManager fleetManager = FleetManager.getInstance();

	/**
	 * The main game class instance.
	 */
	private Main main;

	/**
	 * The hex grid for the galaxy map.
	 */
	private Node grid;

	/**
	 * The Node for highlighting the currently selected star
	 */
	public StarHighlight starHighlight;

	/**
	 * The Node for highlighting the currently selected fleet
	 */
	public FleetHighlight fleetHighlight;

	/**
	 * The Node representing the path from one fleet to a destination star.
	 */
	public FleetRoute fleetRoute;

	/**
	 * 
	 */
//	private transient Iterator<Race> raceIterator;

	/**
	 * 
	 */
//	private transient Iterator<StarNode> starIterator;

	/**
	 * 
	 */
	private transient Iterator<FleetNode> fleetIterator;

	/**
	 * 
	 */
	private transient Event event;

	/**
	 * Default no-arg constructor.
	 */
	private GameManager() {
	}

	/**
	 * Returns the singleton instance of this class.
	 * 
	 * @return the singleton instance of this class
	 */
	public static GameManager getInstance() {
		if (instance == null) {
			instance = new GameManager();
		}
		return instance;
	}

	/**
	 * Do any initialization here.
	 * 
	 * @param main - the main class
	 */
	public void init(Main main) {
		this.main = main;

		/*
		 * Get the singleton instances of HudManager and MenuManager and initializes them.
		 */
		hudManager.init();
		menuManager.init();

		/*
		 * Create the highlight hex grid for highlighting the selected star or colony.
		 */
		this.starHighlight = new StarHighlight();

		/*
		 * Create the highlight hex grid for highlighting the selected fleet.
		 */
		this.fleetHighlight = new FleetHighlight();

		/*
		 * Create the path node showing the line between a fleet and its destination star.
		 */
		this.fleetRoute = new FleetRoute("FleetRoute", main._root);
	}

	/**
	 * Perform the current action.
	 */
	public void performAction() {
		switch (gameData.action) {
		case SHOW_MAIN_MENU:
			menuManager.showMainMenuFrame();
			gameData.action = Actions.NONE;
			break;
		case SHOW_LOAD_GAME_MENU:
			menuManager.showLoadGameFrame();
			gameData.action = Actions.NONE;
			break;
		case SHOW_NEW_GAME_MENU:
			menuManager.showNewGameFrame();
			gameData.action = Actions.NONE;
			break;
		case SHOW_CHOOSE_RACE_MENU:
			menuManager.showChooseRaceFrame();
			gameData.action = Actions.NONE;
			break;
		case SHOW_CHOOSE_BANNER_MENU:
			menuManager.showChooseBannerFrame();
			gameData.action = Actions.NONE;
			break;
		case SHOW_CHOOSE_LEADER_NAME_MENU:
			menuManager.showChooseLeaderNameFrame();
			gameData.action = Actions.NONE;
			break;
		case SHOW_CHOOSE_STAR_NAME_MENU:
			menuManager.showChooseHomeStarFrame();
			gameData.action = Actions.NONE;
			break;
		case SHOW_OPTIONS_MENU:
			menuManager.showOptionsFrame();
			gameData.action = Actions.NONE;
			break;
		case CONTINUE_GAME:
			gameData.playerLeaderName = Race.getRaceLeaderName(gameData.playerRaceIndex);
			gameData.homeStarName = Star.STAR_NAMES[gameData.playerRaceIndex + 1];
//			menuManager.hideMenu();
//			startNewGame();
//			moveStarHighlight();
//			centerOnStar();
//			hudManager.showStarInfo();
//			gameData.action = Actions.NONE;
//			gameData.gameState = GameStates.IN_STAR_INFO;
//			break;
		case START_GAME:
			menuManager.hideMenu();
			startNewGame();
			moveStarHighlight();
			centerOnStar();
			hudManager.showStarInfo();
			gameData.action = Actions.NONE;
			gameData.gameState = GameStates.IN_STAR_INFO;
			break;
		case CAM_UP:
			Vector3 point = Main.control.getLookAtPoint();
			if (point.getY() < gameData.maxWorldY) {
				point.addLocal(0, HexGridUtils.HEXGRID_S + HexGridUtils.HEXGRID_H, 0);
				Main.control.setLookAtPoint(point);
				Main.control.makeDirty();
			}
			gameData.action = Actions.NONE;
			break;
		case CAM_LEFT:
			point = Main.control.getLookAtPoint();
			if (point.getX() > gameData.minWorldX) {
				point.addLocal(-(HexGridUtils.HEXGRID_R * 2), 0, 0);
				Main.control.setLookAtPoint(point);
				Main.control.makeDirty();
			}
			gameData.action = Actions.NONE;
			break;
		case CAM_RIGHT:
			point = Main.control.getLookAtPoint();
			if (point.getX() < gameData.maxWorldX) {
				point.addLocal(HexGridUtils.HEXGRID_R * 2, 0, 0);
				Main.control.setLookAtPoint(point);
				Main.control.makeDirty();
			}
			gameData.action = Actions.NONE;
			break;
		case CAM_DOWN:
			point = Main.control.getLookAtPoint();
			if (point.getY() > gameData.minWorldY) {
				point.addLocal(0, -(HexGridUtils.HEXGRID_S + HexGridUtils.HEXGRID_H), 0);
				Main.control.setLookAtPoint(point);
				Main.control.makeDirty();
			}
			gameData.action = Actions.NONE;
			break;
		case TOGGLE_GRID:
			toggleGrid();
			gameData.action = Actions.NONE;
			break;
		case TILE_CLICKED:
			centerOnTile();
			gameData.action = Actions.NONE;
			break;
		case STAR_PICKED:
			moveStarHighlight();
			hudManager.showStarInfo();
			gameData.action = Actions.NONE;
			break;
		case CENTER_ON_STAR:
			centerOnStar();
			gameData.action = Actions.NONE;
			break;
		case SHOW_PREV_STAR:
			findPreviousStar();
			moveStarHighlight();
			centerOnStar();
			hudManager.showStarInfo();
			gameData.action = Actions.NONE;
			break;
		case SHOW_NEXT_STAR:
			findNextStar();
			moveStarHighlight();
			centerOnStar();
			hudManager.showStarInfo();
			gameData.action = Actions.NONE;
			break;
		case SHOW_PREV_COLONY:
			findPreviousColony();
			moveStarHighlight();
			centerOnStar();
			hudManager.showStarInfo();
			gameData.action = Actions.NONE;
			break;
		case SHOW_NEXT_COLONY:
			findNextColony();
			moveStarHighlight();
			centerOnStar();
			hudManager.showStarInfo();
			gameData.action = Actions.NONE;
			break;
		case FLEET_PICKED:
			moveFleetHiglight();
			hudManager.showFleetInfo();
			gameData.action = Actions.NONE;
			break;
		case CENTER_ON_FLEET:
			centerOnFleet();
			gameData.action = Actions.NONE;
			break;
		case SHOW_PREV_FLEET:
			findPreviousFleet();
			moveFleetHiglight();
			centerOnFleet();
			hudManager.showFleetInfo();
			gameData.action = Actions.NONE;
			break;
		case SHOW_NEXT_FLEET:
			findNextFleet();
			moveFleetHiglight();
			centerOnFleet();
			hudManager.showFleetInfo();
			gameData.action = Actions.NONE;
			break;
		case DESTINATION_PICKED:
			hudManager.updateFleetInfo();
			gameData.action = Actions.NONE;
			break;
		case START_COLONY_PRODUCTION:
			hudManager.updateDateAndTurn();
			doColonySpending();
			gameData.action = Actions.START_FLEET_MOVEMENT;
			gameData.gameState = GameStates.IN_FLEET_MOVEMENT;
			break;
//		case NEXT_COLONY_PRODUCTION:
//			if (starIterator.hasNext()) {
//				logger.info("starIterator.hasNext() = true");
//				StarNode starNode = starIterator.next();
//				Star star = starNode.getStar();
//				if (star.owner != null && star.owner == gameData.playerRace) {
//					Colony colony = (Colony) star;
//					logger.info("colony = " + colony);
//					colony.update();
//				}
//			} else {
//				logger.info("starIterator.hasNext() = false");
//				starIterator = null;
//				gameData.action = Actions.START_FLEET_MOVEMENT;
//				gameData.gameState = GameStates.IN_FLEET_MOVEMENT;
//			}
//			break;
		case START_FLEET_MOVEMENT:
			fleetIterator = gameData.playerFleetList.iterator();
			gameData.action = Actions.MOVE_NEXT_FLEET;
			break;
		case MOVE_NEXT_FLEET:
			if (fleetIterator.hasNext()) {
				logger.info("fleetIterator.hasNext() = true");
				gameData.currentFleetNode = fleetIterator.next();
				logger.info("currentFleetNode = " + gameData.currentFleetNode);
				if (gameData.currentFleetNode.status != FleetStatus.ORBITING) {
					fleetManager.prepareToMoveFleet(gameData.currentFleetNode);
					gameData.action = Actions.MOVE_CURRENT_FLEET;
				}
			} else {
				logger.info("fleetIterator.hasNext() = false");
				GameData.clearDiscardList();
				fleetIterator = null;
				gameData.action = Actions.SHOW_EVENT;
				gameData.gameState = GameStates.IN_SHOW_EVENT;
			}
			break;
		case MOVE_CURRENT_FLEET:
			boolean done = fleetManager.moveFleet(gameData.currentFleetNode);
			if (done) {
				gameData.action = Actions.MOVE_NEXT_FLEET;
			}
			break;
		case SHOW_EVENT:
			if (gameData.eventQueue.isEmpty()) {
				event = null;
				gameData.currentStarNode = gameData.playerHomeStarNode;
				moveStarHighlight();
				centerOnStar();
				hudManager.showStarInfo();
				gameData.action = Actions.NONE;
				gameData.gameState = GameStates.IN_STAR_INFO;
			} else {
				event = gameData.eventQueue.poll();
				moveStarHighlight(event.starNode);
				centerOnStar();
				if (event instanceof ExploreEvent) {
					gameData.action = Actions.SHOW_EXPLORE_EVENT;
				} else {
					gameData.action = Actions.SHOW_COLONIZE_EVENT;
				}
			}
			break;
		case SHOW_EXPLORE_EVENT:
			showExploreEvent((ExploreEvent) event);
			gameData.action = Actions.NONE;
			break;
		case SHOW_COLONIZE_EVENT:
			showColonizeEvent((ColonizeEvent) event);
			gameData.action = Actions.NONE;
			break;
		case SHOW_COLONIZED_INFO:
			colonizeStar(event.starNode, event.fleetNode);
			gameData.action = Actions.NONE;
			break;
		case HIDE_COLONIZED_INFO:
			hudManager.hideColonizedStarInfo();
			gameData.action = Actions.SHOW_EVENT;
			break;
		case EXIT_GAME:
			main.exit();
			break;
		default:
			gameData.action = Actions.NONE;
		}
	}

	/**
	 * Starts a new game with the selected galaxy size, difficulty, number of opponents, and the selected race and banner color.
	 */
	private void startNewGame() {
		switch (gameData.galaxySizeIndex) {
		case 0: // small (orig: 213x180)
			gameData.galaxyWidth = gameData.galaxyHeight = 24;
			gameData.starCount = 24;
			break;
		case 1: // medium (orig: 273x243)
			gameData.galaxyWidth = gameData.galaxyHeight = 24;
			gameData.starCount = 48;
			break;
		case 2: // large (orig: 333x278)
			gameData.galaxyWidth = gameData.galaxyHeight = 28;
			gameData.starCount = 72;
			break;
		default: // huge (orig: 389x338)
			gameData.galaxyWidth = gameData.galaxyHeight = 32;
			gameData.starCount = 108;
		}

		logger.info("galaxySizeIndex = " + gameData.galaxySizeIndex);
		logger.info("galaxyWidth = " + gameData.galaxyWidth);
		logger.info("galaxyHeight = " + gameData.galaxyHeight);
		logger.info("starCount = " + gameData.starCount);
		logger.info("difficultyIndex = " + gameData.difficultyIndex);
		logger.info("raceCount = " + gameData.raceCount);
		logger.info("playerRaceIndex = " + gameData.playerRaceIndex);
		logger.info("playerBannerIndex = " + gameData.playerBannerIndex);
		logger.info("playerLeaderName = " + gameData.playerLeaderName);
		logger.info("homeStarName = " + gameData.homeStarName);

		/*
		 * Set the scoll limits based on the galaxy size.
		 */
		gameData.setScrollLimits();

		/*
		 * Create the Grid and Path nodes.
		 */
		createGrid();

		/*
		 * Create the player and AI races.
		 */
		createRaces();

		/*
		 * Create the stars in the game.
		 */
		createStars();

		/*
		 * Create the starting fleet for the player race.
		 */
		createPlayerInitialFleet();

		/*
		 * Initializes the game date and the game turn number. Also display the banner at the top of the screen.
		 */
		gameData.gameYear = 2400;
		gameData.gameMonth = 0;
		gameData.gameTurn = 0;
		hudManager.showScreenBanner();

		// TESTING

//		/*
//		 * Create the Torus (testing).
//		 */
//		createTestSpatial();

//		/*
//		 * Create the Model (testing).
//		 */
//		loadTestModel();
	}

	/**
	 * Comment for method.
	 *
	 */
//	private void createTestSpatial() {
//		final BillboardNode billboard = new BillboardNode("Billboard");
//		billboard.setAlignment(BillboardAlignment.ScreenAligned);
//		main._root.attachChild(billboard);
//
//		final Quad quad = new Quad("Quad", 0.425, 0.490);
//		billboard.attachChild(quad);
//
//		final TextureState ts = new TextureState();
//		ts.setEnabled(true);
//		ts.setTexture(TextureManager.load("images/moo2_banner_blue.png", Texture.MinificationFilter.Trilinear, TextureStoreFormat.GuessCompressedFormat, true));
//		billboard.setRenderState(ts);
//
//		final MaterialState ms = new MaterialState();
//		ms.setEmissive(ColorRGBA.WHITE);
//		ms.setEnabled(true);
//		billboard.setRenderState(ms);
//
//		// torusNode.setTranslation(gameData.playerHomeStarNode.getTranslation());
//	}

//	/**
//	 * Comment for method.
//	 *
//	 */
//	private void loadTestModel() {
//		/*
//		 * Load a .DAE model
//		 */
//		ColladaImporter importer = new ColladaImporter();
//		ColladaStorage storage;
//		try {
//			storage = importer.load("models/dae/ship.dae");
//		} catch (IOException ex) {
//			logger.error("Exception when loading the ship model", ex);
//			return;
//		}
//		Node model = storage.getScene();
//		model.updateWorldBound(true);
//		model.setScale(0.1);
//
////		/*
////		 * Load a .OBJ model
////		 */
////		final ObjImporter importer = new ObjImporter();
////		final ObjGeometryStore storage = importer.load("models/obj/SPINDRIFT.obj");
////		Node model = storage.getScenegraph();
////		model.updateWorldBound(true);
////		model.setScale(0.1);
////
////		final Quaternion rotation = Quaternion.fetchTempInstance();
////		rotation.fromAngleNormalAxis(90.0 * MathUtils.DEG_TO_RAD, Vector3.UNIT_X);
////		model.setRotation(rotation);
////		Quaternion.releaseTempInstance(rotation);
////
////		final Vector3 translation = Vector3.fetchTempInstance();
////		translation.set(gameData.playerHomeStarNode.getTranslation());
////		translation.addLocal(0, 0, StarNode.STAR_SPHERE_RADIUS);
////		model.setTranslation(translation);
////		Vector3.releaseTempInstance(translation);
//
//		main._root.attachChild(model);
//	}

	/**
	 * Create a grid on the galaxy map 1 parsec wide apart.
	 */
	private void createGrid() {
		grid = new Node("Grid");
		main._root.attachChild(grid);

		final MaterialState renderState = new MaterialState();
		renderState.setAmbient(ColorRGBA.DARK_GRAY);

		double[][] tuples3 = HexGridUtils.getLowerRightLowerLeftLeftSegmentVertices(null);
		final ReadOnlyVector3[] verticesThreeLRLLL = { new Vector3(tuples3[0][0], tuples3[0][1], 0), new Vector3(tuples3[1][0], tuples3[1][1], 0), new Vector3(tuples3[1][0], tuples3[1][1], 0), new Vector3(tuples3[2][0], tuples3[2][1], 0),
				new Vector3(tuples3[2][0], tuples3[2][1], 0), new Vector3(tuples3[3][0], tuples3[3][1], 0) };
		final Line hex_LR_LL_L = new Line("3-segment-lower-right-lower-left-left", verticesThreeLRLLL, null, null, null);
		hex_LR_LL_L.updateModelBound();
		hex_LR_LL_L.setRenderState(renderState);

		double[][] tuples2 = HexGridUtils.getUpperRightRightSegmentVertices(null);
		final ReadOnlyVector3[] verticesTwoURR = { new Vector3(tuples2[0][0], tuples2[0][1], 0), new Vector3(tuples2[1][0], tuples2[1][1], 0), new Vector3(tuples2[1][0], tuples2[1][1], 0), new Vector3(tuples2[2][0], tuples2[2][1], 0) };
		final Line hex_UR_R = new Line("2-segment-upper-right-right", verticesTwoURR, null, null, null);
		hex_UR_R.updateModelBound();
		hex_UR_R.setRenderState(renderState);

		double[][] tuples1 = HexGridUtils.getRightSegmentVertices(null);
		final ReadOnlyVector3[] verticesOneR = { new Vector3(tuples1[0][0], tuples1[0][1], 0), new Vector3(tuples1[1][0], tuples1[1][1], 0) };
		final Line hex_R = new Line("1-segment-right", verticesOneR, null, null, null);
		hex_R.updateModelBound();
		hex_R.setRenderState(renderState);

		tuples1 = HexGridUtils.getUpperLeftSegmentVertices(tuples1);
		final ReadOnlyVector3[] verticesOneUL = { new Vector3(tuples1[0][0], tuples1[0][1], 0), new Vector3(tuples1[1][0], tuples1[1][1], 0) };
		final Line hex_UL = new Line("1-segment-upper-left", verticesOneUL, null, null, null);
		hex_UL.updateModelBound();
		hex_UL.setRenderState(renderState);

		double[] origin = new double[2];

		/*
		 * The main grid (mostly)
		 */
		for (int x = 0; x < gameData.galaxyWidth; x++) {
			for (int y = 0; y < gameData.galaxyHeight; y++) {
				final Mesh sm = hex_LR_LL_L.makeCopy(true);
				origin = HexGridUtils.getHexWorldOrigin(x, y, origin);
				sm.setTranslation(origin[0], origin[1], 0);
				grid.attachChild(sm);
			}
		}

		/*
		 * Close the loop on the upper right and right sides
		 */
		for (int y = 0; y < gameData.galaxyHeight; y++) {
			final Mesh sm = hex_UR_R.makeCopy(true);
			origin = HexGridUtils.getHexWorldOrigin(gameData.galaxyWidth - 1, y, origin);
			sm.setTranslation(origin[0], origin[1], 0);
			grid.attachChild(sm);
		}

		/*
		 * First y on the left
		 */
		for (int x = 0; x < gameData.galaxyWidth - 1; x++) {
			final Mesh sm = hex_R.makeCopy(true);
			origin = HexGridUtils.getHexWorldOrigin(x, 0, origin);
			sm.setTranslation(origin[0], origin[1], 0);
			grid.attachChild(sm);
		}

		/*
		 * Last y on the right
		 */
		for (int x = 0; x < gameData.galaxyWidth; x++) {
			final Mesh sm = hex_UL.makeCopy(true);
			origin = HexGridUtils.getHexWorldOrigin(x, gameData.galaxyHeight - 1, origin);
			sm.setTranslation(origin[0], origin[1], 0);
			grid.attachChild(sm);
		}
	}

	/**
	 * Create a the array of selected player race plus the random opponents.
	 */
	private void createRaces() {
		/*
		 * Create the array of races (the player's race is at index 0).
		 */
		gameData.races = new Race[gameData.raceCount];

		/*
		 * Create the player's selected race.
		 */
		gameData.playerRace = gameData.races[0] = Race.createPlayerRace(gameData.playerRaceIndex, gameData.playerBannerIndex, gameData.playerLeaderName);
		logger.info("Player race: " + gameData.playerRace);
		logger.info("Player home: " + gameData.homeStarName);

		/*
		 * Create the AI's races.
		 */
		for (int index = 1; index < gameData.races.length; index++) {
			gameData.races[index] = Race.createRandomRace();
			logger.info("AI race #" + index + ": " + gameData.races[index]);
		}
	}

	/**
	 * Create all the stars in the galaxy. The first star created is Orion. Then the player's home star, followed by each of the
	 * AI players' home stars. Then the remaining unexplored stars will be created.
	 */
	private void createStars() {
		/*
		 * Create the list of stars based on the galaxy size.
		 */
		gameData.starList.clear();
		gameData.starList.ensureCapacity(gameData.starCount);

		/*
		 * Initialize galaxy bitmap. Whenever a star is placed, the tile and its surrounding tiles are marked as unavailable.
		 */
		boolean[][] bitmap = new boolean[gameData.galaxyWidth][gameData.galaxyHeight];

		/*
		 * Create and place the star Orion randomly somewhere in the galaxy. Do not place the star along each of the 4 edges.
		 */
		int x = MathUtils.nextRandomInt(2, gameData.galaxyWidth - 3);
		int y = MathUtils.nextRandomInt(2, gameData.galaxyHeight - 3);
		updateBitmap(bitmap, x, y, true);

		/*
		 * Create the node representing Orion and add it to the list of nodes representing stars.
		 */
		StarNode orionNode = StarNode.createOrionNode(x, y);
		main._root.attachChild(orionNode);
		gameData.starList.add(orionNode);

		/*
		 * The galaxy is divided into 3 by 2 sectors. Each home system will be placed in a sector. Depending on the number of
		 * opponents selected, some sectors will be unoccupied.
		 */
		ArrayList<Point> homePoints = new ArrayList<Point>(6); // maximum of 6 home worlds (player + 5 AI races)
		int sectorWidth = gameData.galaxyWidth / 3;
		int sectorHeight = gameData.galaxyHeight / 2;
		int minCol = 2;
		int maxCol = sectorWidth - 3;
		int minRow;
		int maxRow;
		for (int col = 0; col < 3; col++) {
			minRow = 2;
			maxRow = sectorHeight - 3;
			for (int row = 0; row < 2; row++) {
				do {
					x = MathUtils.nextRandomInt(minCol, maxCol);
					y = MathUtils.nextRandomInt(minRow, maxRow);
				} while (bitmap[x][y]);
				homePoints.add(new Point(x, y));
				updateBitmap(bitmap, x, y, false);
				minRow += sectorHeight;
				maxRow += sectorHeight;
			}
			minCol += sectorWidth;
			maxCol += sectorWidth;
		}

		/*
		 * Create the node representing the player's home world and add it to the list of nodes representing stars.
		 */
		Point point = homePoints.remove(MathUtils.nextRandomInt(0, homePoints.size() - 1));
		gameData.playerHomeStarNode = StarNode.createPlayerHomeStarNode(point.x, point.y);
		main._root.attachChild(gameData.playerHomeStarNode);
//		orionNode.setRange(gameData.playerHomeStarNode);
		gameData.starList.add(gameData.playerHomeStarNode);

		/*
		 * Create the node representing each AI player's home world and add them to the list of nodes representing stars.
		 */
		for (int index = 1; index < gameData.races.length; index++) {
			if (homePoints.size() == 1)
				point = homePoints.get(0);
			else
				point = homePoints.remove(MathUtils.nextRandomInt(0, homePoints.size() - 1));
			StarNode aiHomeStarNode = StarNode.createAiHomeStarNode(point.x, point.y, gameData.races[index]);
//			aiHomeStarNode.setRange(gameData.playerHomeStarNode);
			main._root.attachChild(aiHomeStarNode);
			gameData.starList.add(aiHomeStarNode);
		}

		/*
		 * Randomly place the rest of the stars on the galaxy map.
		 */
		minCol = 2;
		maxCol = gameData.galaxyWidth - 3;
		minRow = 2;
		maxRow = gameData.galaxyHeight - 3;
		for (int index = gameData.raceCount; index < gameData.starCount; index++) { // orion + player + AI races
			do {
				x = MathUtils.nextRandomInt(minCol, maxCol);
				y = MathUtils.nextRandomInt(minRow, maxRow);
			} while (bitmap[x][y]);
			updateBitmap(bitmap, x, y, true);
			StarNode starNode = StarNode.createRandomStarNode(x, y);
//			starNode.setRange(gameData.playerHomeStarNode);
			main._root.attachChild(starNode);
			gameData.starList.add(starNode);
		}

		/*
		 * The current star is the player's home world
		 */
		gameData.currentStarNode = gameData.playerHomeStarNode;
	}

	/**
	 * Once a star is placed, mark its tile and the surrounding tiles as being unavailable.
	 * 
	 * @param bitmap - the bitmap array
	 * @param x - the x-coordinate of the star
	 * @param y - the y-coordinate of the star
	 * @param isThree - true if radius is 3
	 */
	private static void updateBitmap(boolean[][] bitmap, int x, int y, boolean isThree) {
		bitmap[x][y] = true;
		/*
		 * 1 parsec around
		 */
		bitmap[x - 1][y + 1] = true;
		bitmap[x][y + 1] = true;
		bitmap[x + 1][y] = true;
		bitmap[x + 1][y - 1] = true;
		bitmap[x][y - 1] = true;
		bitmap[x - 1][y] = true;
		/*
		 * 2 parsecs around
		 */
//		if (isThree) {
			bitmap[x - 2][y] = true;
			bitmap[x - 2][y + 1] = true;
			bitmap[x - 2][y + 2] = true;
			bitmap[x - 1][y + 2] = true;
			bitmap[x - 1][y - 1] = true;
			bitmap[x][y + 2] = true;
			bitmap[x][y - 2] = true;
			bitmap[x + 1][y + 1] = true;
			bitmap[x + 1][y - 2] = true;
			bitmap[x + 2][y] = true;
			bitmap[x + 2][y - 1] = true;
			bitmap[x + 2][y - 2] = true;
//		}
	}

	/**
	 * Create the initial fleet for the player race.
	 */
	private void createPlayerInitialFleet() {
		/*
		 * Initialize the list of fleets for the player race.
		 */
		gameData.playerFleetList = GameData.getFleetList(gameData.playerRace);
		gameData.playerFleetList.clear();
		gameData.playerFleetList.ensureCapacity(4);

		/*
		 * Create the FleetNode object for the player race.
		 */
		gameData.currentFleetNode = FleetNode.createInitialFleet(gameData.playerRace);

		/*
		 * Put the FleetNode into orbit around the player home world
		 */
		fleetManager.putFleetInOrbit(gameData.playerHomeStarNode, gameData.currentFleetNode);

		/*
		 * Add the FleetNode to the scene graph
		 */
		main._root.attachChild(gameData.currentFleetNode);
	}

	/**
	 * Toggle the display of the hex grid on and off.
	 */
	private void toggleGrid() {
		if (main._root.hasChild(grid))
			main._root.detachChild(grid);
		else
			main._root.attachChild(grid);
	}

	/**
	 * Center the clicked tile.
	 */
	private void centerOnTile() {
		Main.control.setLookAtPoint(gameData.clickedVector);
	}

	/**
	 * Update the position of the highlight based on the currently selected star.
	 */
	private void moveStarHighlight() {
		this.moveStarHighlight(gameData.currentStarNode);
	}

	/**
	 * Update the position of the highlight based on the specified star.
	 *
	 * @param starNode - the StarNode to move the highlight to
	 */
	private void moveStarHighlight(StarNode starNode) {
		logger.info("Inside moveStarHighlight...");
		if (!main._root.hasChild(starHighlight)) {
			main._root.detachChild(fleetHighlight);
			main._root.attachChild(starHighlight);
		}
		starHighlight.setTranslation(starNode.getTranslation());
		logger.info("...leaving moveStarHighlight");
	}

	/**
	 * Update the position of the highlight based on the currently selected fleet.
	 */
	private void moveFleetHiglight() {
		this.moveFleetHiglight(gameData.currentFleetNode);
	}

	/**
	 * Update the position of the highlight to the specified fleet.
	 */
	private void moveFleetHiglight(FleetNode fleetNode) {
		if (!main._root.hasChild(fleetHighlight)) {
			main._root.detachChild(starHighlight);
			main._root.attachChild(fleetHighlight);
		}
		fleetHighlight.setTranslation(fleetNode.getTranslation());
	}

	/**
	 * Center the currently selected fleet on the screen.
	 */
	private void centerOnStar() {
		logger.info("Inside centerOnStar...");
//		if (!main._root.hasChild(starHighlight)) {
//			main._root.detachChild(fleetHighlight);
//			main._root.attachChild(starHighlight);
//		}
		Main.control.setLookAtSpatial(starHighlight);
		logger.info("...leaving centerOnStar");
	}

	/**
	 * Center the currently selected star on the screen.
	 */
	private void centerOnFleet() {
//		if (!main._root.hasChild(fleetHighlight)) {
//			main._root.detachChild(starHighlight);
//			main._root.attachChild(fleetHighlight);
//		}
		Main.control.setLookAtSpatial(fleetHighlight);
	}

	/**
	 * Find the previous star. The new star node is stored in <code>gameData.currentStarNode</code>.
	 */
	private void findPreviousStar() {
		int index = gameData.starList.indexOf(gameData.currentStarNode);
		if (index == 0)
			index = gameData.starList.size();
		index--;
		gameData.currentStarNode = gameData.starList.get(index);
	}

	/**
	 * Find the next star. The new star node is stored in <code>gameData.currentStarNode</code>.
	 */
	private void findNextStar() {
		int index = gameData.starList.indexOf(gameData.currentStarNode);
		index++;
		if (index == gameData.starList.size())
			index = 0;
		gameData.currentStarNode = gameData.starList.get(index);
	}

	/**
	 * Find the previous colony belonging to the player. The new colony is stored in <code>gameData.currentStarNode</code>.
	 */
	private void findPreviousColony() {
		int index = gameData.starList.indexOf(gameData.currentStarNode);
		do {
			if (index == 0)
				index = gameData.starList.size();
			index--;
			gameData.currentStarNode = gameData.starList.get(index);
		} while (gameData.currentStarNode.getStar().owner != gameData.playerRace);
	}

	/**
	 * Find the next colony belonging to the player. The new colony is stored in <code>gameData.currentStarNode</code>.
	 */
	private void findNextColony() {
		int index = gameData.starList.indexOf(gameData.currentStarNode);
		do {
			index++;
			if (index == gameData.starList.size())
				index = 0;
			gameData.currentStarNode = gameData.starList.get(index);
		} while (gameData.currentStarNode.getStar().owner != gameData.playerRace);
	}

	/**
	 * Find the previous fleet in the list belonging to the player. The new fleet is stored in <code>gameData.currentFleetNode</code>.
	 */
	private void findPreviousFleet() {
		int index = gameData.playerFleetList.indexOf(gameData.currentFleetNode);
		if (index == -1) { // current fleet is not a player fleet
			index = 0;
		} else {
			if (index == 0)
				index = gameData.playerFleetList.size();
			index--;
		}
		gameData.currentFleetNode = gameData.playerFleetList.get(index);
	}

	/**
	 * Find the next fleet in the list belonging to the player. The new fleet is stored in <code>gameData.currentFleetNode</code>.
	 */
	private void findNextFleet() {
		int index = gameData.playerFleetList.indexOf(gameData.currentFleetNode);
		if (index == -1) { // current fleet is not a player fleet
			index = 0;
		} else {
			index++;
			if (index == gameData.playerFleetList.size())
				index = 0;
		}
		gameData.currentFleetNode = gameData.playerFleetList.get(index);
	}

	/**
	 * Perform the spending for each colony.
	 */
	private void doColonySpending() {
		for (StarNode starNode : gameData.starList) {
			Star star = starNode.getStar();
			if (star.owner != null) {
				Colony colony = (Colony) star;
				logger.info("doColonySpending(): name: " + colony.name + ", owner: " + colony.owner);

				int ships = colony.doShipSpending();
				logger.info("doColonySpending(): " + ships + " ships built on " + colony.name);
				colony.doDefenseSpending();
				colony.doIndustrySpending();
				colony.doEcologySpending();
				colony.doResearchSpending();

				colony.doPopulationGrowth();

				colony.updateBaseProduction();
				colony.updateNetProduction();

				colony.updateShipText();
				colony.updateDefenseText();
				colony.updateIndustryText();
				colony.updateEcologyText();
				colony.updateResearchText();
			}
		}
	}

	/**
	 * Show the given Explore Event.
	 * 
	 * @param event - the Event to show
	 */
	private void showExploreEvent(ExploreEvent event) {
		StarNode starNode = event.starNode;
		Star star = starNode.getStar();
		star.explored = true;
		boolean colonizable = gameData.playerRace.maxControlledEnvironment.ordinal() <= star.environment.ordinal();
		ReadOnlyColorRGBA textColor = (colonizable) ? GameData.COLOR_DARK_GREEN : GameData.COLOR_DARK_RED;
		starNode.addStarNameText(textColor);
		fleetManager.putFleetInOrbit(starNode, event.fleetNode);
		hudManager.showExploringStarInfo(starNode);
	}

	/**
	 * Show the given Colonize Event.
	 * 
	 * @param event - the Event to show
	 */
	private void showColonizeEvent(ColonizeEvent event) {
		StarNode starNode = event.starNode;
		fleetManager.putFleetInOrbit(starNode, event.fleetNode);
		hudManager.showColonizingStarInfo(starNode);
	}

	/**
	 * Implement the colonization steps.
	 *
	 * @param starNode - the Star to colonize
	 * @param fleetNode - the Fleet involved
	 */
	private void colonizeStar(StarNode starNode, FleetNode fleetNode) {
		starNode.colonize(gameData.playerRace);
//		fleetManager.removeColonyShipFromFleet(fleetNode);
		hudManager.showColonizedStarInfo(event.starNode);
	}

}
